#light


namespace Langexplr.Abc

open Langexplr.Abc
open System.IO

type TraitType =
  | TraitSlot of int * int * int * byte
  | TraitMethod of int * int
  | TraitGetter of int * int
  | TraitSetter of int * int
  | TraitClass of int * int // TODO(incomplete)
  | TraitFunction of int * int
  | TraitConst of int * int * int * byte
with
   static member ReadFrom(input : BinaryReader) =

     let inputByte = int(input.ReadByte())
     let atts = (inputByte >>> 4)   
     let typeId = (inputByte &&& 0x0F)     
     in       
      (  atts,
         match typeId with 
            | 0 -> let slotId = int(TypeEncodingUtilities.ReadU30Value(input))
                   let typeName = int(TypeEncodingUtilities.ReadU30Value(input))
                   let vIndex = int(TypeEncodingUtilities.ReadU30Value(input))
                   TraitSlot(slotId,
                             typeName,
                             vIndex,
                             if (vIndex <> 0) then input.ReadByte() else byte(0))
            | 1 -> TraitMethod(int(TypeEncodingUtilities.ReadU30Value(input)),int(TypeEncodingUtilities.ReadU30Value(input)))
            | 2 -> TraitGetter(int(TypeEncodingUtilities.ReadU30Value(input)),int(TypeEncodingUtilities.ReadU30Value(input)))
            | 3 -> TraitSetter(int(TypeEncodingUtilities.ReadU30Value(input)),int(TypeEncodingUtilities.ReadU30Value(input)))
            | 4 -> TraitClass(int(TypeEncodingUtilities.ReadU30Value(input)),int(TypeEncodingUtilities.ReadU30Value(input)))
            | 5 -> TraitFunction(int(TypeEncodingUtilities.ReadU30Value(input)),int(TypeEncodingUtilities.ReadU30Value(input)))
            | 6 -> let slotId = int(TypeEncodingUtilities.ReadU30Value(input))
                   let typeName = int(TypeEncodingUtilities.ReadU30Value(input))
                   let vIndex = int(TypeEncodingUtilities.ReadU30Value(input))
                   TraitConst(slotId,
                              typeName,
                              vIndex,
                              if (vIndex <> 0) then input.ReadByte() else byte(0))
            | _ -> raise (new System.InvalidOperationException(sprintf "Invalid trait type found %d" typeId)))

   member this.WriteTo(atts:byte,output : BinaryWriter) =         
     let firstByte = atts <<< 4
     in 
         match this with 
            | TraitSlot(slotId,typeName,vIndex,theValIndex) -> 
                   output.Write(byte(firstByte ||| byte(0)))
                   TypeEncodingUtilities.WriteU30Value(uint32(slotId),output)
                   TypeEncodingUtilities.WriteU30Value(uint32(typeName),output)
                   TypeEncodingUtilities.WriteU30Value(uint32(vIndex),output)
                   if vIndex <> 0 then output.Write(theValIndex)
                   else ()
                   
            | TraitMethod(dispId,methodIndex) ->
                   output.Write(byte(firstByte ||| byte(1)))
                   TypeEncodingUtilities.WriteU30Value(uint32(dispId),output)
                   TypeEncodingUtilities.WriteU30Value(uint32(methodIndex),output)
                 
            | TraitGetter(dispId,methodIndex) ->
                   output.Write(byte(firstByte ||| byte(2)))
                   TypeEncodingUtilities.WriteU30Value(uint32(dispId),output)
                   TypeEncodingUtilities.WriteU30Value(uint32(methodIndex),output)

            | TraitSetter(dispId,methodIndex) ->
                   output.Write(byte(firstByte ||| byte(3)))
                   TypeEncodingUtilities.WriteU30Value(uint32(dispId),output)
                   TypeEncodingUtilities.WriteU30Value(uint32(methodIndex),output)
                   
            | TraitClass(cInit,traitCount) ->
                  output.Write(byte(firstByte ||| byte(4)))
                  TypeEncodingUtilities.WriteU30Value(uint32(cInit),output)
                  TypeEncodingUtilities.WriteU30Value(uint32(traitCount),output)
               
            | TraitFunction(slotId,functionIndex) ->
                  output.Write(byte(firstByte ||| byte(5)))
                  TypeEncodingUtilities.WriteU30Value(uint32(slotId),output)
                  TypeEncodingUtilities.WriteU30Value(uint32(functionIndex),output)
                  
            | TraitConst(slotId,typeName,vIndex,vIndex2) ->
                  output.Write(byte(firstByte ||| byte(6)))
                  TypeEncodingUtilities.WriteU30Value(uint32(slotId),output)
                  TypeEncodingUtilities.WriteU30Value(uint32(typeName),output)
                  TypeEncodingUtilities.WriteU30Value(uint32(vIndex),output)
                  output.Write(vIndex2)
                  

end  


type AbcTraitAttribute =
     | Nothing = 0x0
     | Final = 0x1
     | Override = 0x2

type AbcTraitsInfo =
 {
  name : int;
  traitType : TraitType
  metadata : int array
  attributes : AbcTraitAttribute
 }
with
  static member ReadFrom(input : BinaryReader) =
     let name = int(TypeEncodingUtilities.ReadU30Value(input))
     let (atts,traitType)  = TraitType.ReadFrom(input)
     let traitAtts = 
             (if ( atts &&& 0x1 <> 0 ) 
                then AbcTraitAttribute.Final
                else AbcTraitAttribute.Nothing) |||
             (if ( atts &&& 0x2 <> 0 ) 
                then AbcTraitAttribute.Override
                else AbcTraitAttribute.Nothing)
    
     let metadataCount = 
            if (atts &&& 0x4 <> 0) 
              then int(TypeEncodingUtilities.ReadU30Value(input)) 
              else 0
     let metadata = 
            [|for _ in 1 .. metadataCount -> 
                  int(TypeEncodingUtilities.ReadU30Value(input)) |]
     {
       name = name
       traitType = traitType
       metadata = metadata
       attributes = traitAtts
     }
  member this.WriteTo(output : BinaryWriter) =
      TypeEncodingUtilities.WriteU30Value(uint32(this.name),output)
      
      let outputAtts = byte(AbcTraitAttribute.Final &&& this.attributes) |||
                       byte(AbcTraitAttribute.Override &&& this.attributes)
      
      this.traitType.WriteTo(
              (if this.metadata.Length <> 0 
                  then byte(0x4) 
                  else byte(0)) ||| outputAtts,output)
      Array.iter( 
        fun metadataIndex -> 
            TypeEncodingUtilities.WriteU30Value(
                        uint32(metadataIndex),
                        output)) this.metadata
      
      
      
end
